﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;

namespace Hive.Core.Model
{
    /// <summary>
    /// Describes how a source repository has been configured.
    /// </summary>
    public abstract class SourceRepositoryConfiguration : Configuration
    {
        private const string MappingKey = "Mapping";
        private readonly List<Mapping> mappings;

        /// <summary>
        /// Creates a source repository configuration.
        /// </summary>
        protected SourceRepositoryConfiguration()
        {
            mappings = new List<Mapping>();
        }

        /// <summary>
        /// Creates a source repository configuration from a descriptor.
        /// </summary>
        /// <param name="descriptor">The descriptor.</param>
        /// <exception cref="ArgumentNullException">Thrown if <paramref name="descriptor"/> is null.</exception>
        protected SourceRepositoryConfiguration(Descriptor descriptor)
            : base(descriptor)
        {
            mappings = new List<Mapping>();

            foreach (Descriptor mappingDescriptor in descriptor.GetChildren(MappingKey))
                mappings.Add(Mapping.FromDescriptor(mappingDescriptor));
        }

        /// <summary>
        /// Creates the source repository from the configuration.
        /// </summary>
        /// <returns>The source repository.</returns>
        public abstract ISourceRepository CreateSourceRepository();

        /// <summary>
        /// Gets the read-only list of mappings.
        /// </summary>
        public IList<Mapping> Mappings
        {
            get { return new ReadOnlyCollection<Mapping>(mappings); }
        }

        /// <summary>
        /// Maps packages and branches that match a pattern to a repository root.
        /// </summary>
        /// <param name="packageBranchIdRegex">The regular expression for the package branch.</param>
        /// <param name="repositoryRootFormat">The format string from which to generate the
        /// repository rool url where '{package}' acts as a placeholder for the package name and '{branch}'
        /// acts as a placeholder for the branch name.</param>
        /// <exception cref="ArgumentNullException">Thrown if <paramref name="packageBranchIdRegex"/>
        /// or <paramref name="repositoryRootFormat"/> is null.</exception>
        public void AddMapping(Regex packageBranchIdRegex, string repositoryRootFormat)
        {
            AddMapping(new Mapping(packageBranchIdRegex, repositoryRootFormat));
        }

        /// <summary>
        /// Maps packages and branches that match a pattern to a repository root.
        /// </summary>
        /// <param name="mapping">The mapping to add.</param>
        /// <exception cref="ArgumentNullException">Thrown if <paramref name="mapping"/> is null.</exception>
        public void AddMapping(Mapping mapping)
        {
            if (mapping == null)
                throw new ArgumentNullException("mapping");

            mappings.Add(mapping);
        }

        /// <summary>
        /// Removes a mapping.
        /// </summary>
        /// <param name="mapping">The mapping to add.</param>
        /// <returns>True if the mapping previously existed and was removed.</returns>
        /// <exception cref="ArgumentNullException">Thrown if <paramref name="mapping"/> is null.</exception>
        public bool RemoveMapping(Mapping mapping)
        {
            if (mapping == null)
                throw new ArgumentNullException("mapping");

            return mappings.Remove(mapping);
        }

        /// <summary>
        /// Removes all mappings.
        /// </summary>
        public void ClearMappings()
        {
            mappings.Clear();
        }

        /// <summary>
        /// Gets the repository root url for a particular package branch, or null if not available.
        /// </summary>
        /// <param name="packageName">The package name.</param>
        /// <param name="branchName">The branch name.</param>
        /// <exception cref="ArgumentNullException">Thrown if <paramref name="packageName"/>
        /// or <paramref name="branchName"/> is null.</exception>
        /// <returns>The repository root url or null if not available.</returns>
        public string GetRepositoryRoot(string packageName, string branchName)
        {
            if (packageName == null)
                throw new ArgumentNullException("packageName");
            if (branchName == null)
                throw new ArgumentNullException("branchName");

            string packageBranchId = string.Concat(packageName, "/", branchName);

            foreach (Mapping mapping in mappings)
            {
                if (mapping.PackageBranchIdRegex.IsMatch(packageBranchId))
                {
                    return mapping.RepositoryRootFormat
                        .Replace("{package}", packageName)
                        .Replace("{branch}", branchName);
                }
            }

            return null;
        }

        /// <inheritdoc />
        protected override void PopulateDescriptor(Descriptor descriptor)
        {
            base.PopulateDescriptor(descriptor);

            foreach (var mapping in mappings)
                descriptor.AddChild(MappingKey, mapping.ToDescriptor());
        }

        /// <summary>
        /// Describes a source repository mapping.
        /// </summary>
        public sealed class Mapping
        {
            private const string PackageBranchIdRegexKey = "PackageBranchIdRegex";
            private const string RepositoryRootFormatKey = "RepositoryRootFormat";

            private readonly Regex packageBranchIdRegex;
            private readonly string repositoryRootFormat;

            /// <summary>
            /// Creates a mapping.
            /// </summary>
            /// <param name="packageBranchIdRegex">The regular expression for the package branch id.</param>
            /// <param name="repositoryRootFormat">The format string from which to generate the
            /// repository rool url where '{package}' acts as a placeholder for the package name and '{branch}'
            /// acts as a placeholder for the branch name.</param>
            /// <exception cref="ArgumentNullException">Thrown if <paramref name="packageBranchIdRegex"/> or <paramref name="repositoryRootFormat"/> is null.</exception>
            public Mapping(Regex packageBranchIdRegex, string repositoryRootFormat)
            {
                if (packageBranchIdRegex == null)
                    throw new ArgumentNullException("packageBranchIdRegex");
                if (repositoryRootFormat == null)
                    throw new ArgumentNullException("repositoryRootFormat");

                this.packageBranchIdRegex = packageBranchIdRegex;
                this.repositoryRootFormat = repositoryRootFormat;
            }

            /// <summary>
            /// Gets the regular expression for the package branch id.
            /// </summary>
            /// <remarks>
            /// <para>
            /// A package branch id is a string of the form "{package}/{branch}" that
            /// uniquely identifies a particular version of a binary artifact.
            /// Example: "myproject/trunk/1.0"
            /// </para>
            /// </remarks>
            public Regex PackageBranchIdRegex
            {
                get { return packageBranchIdRegex; }
            }

            /// <summary>
            /// Gets the repository root format string.
            /// </summary>
            /// <value>
            /// The format string from which to generate the repository rool url where '{package}' acts
            /// as a placeholder for the package name and '{branch}' acts as a placeholder for the branch name.
            /// </value>
            public string RepositoryRootFormat
            {
                get { return repositoryRootFormat; }
            }

            internal static Mapping FromDescriptor(Descriptor descriptor)
            {
                string packageBranchIdRegex = descriptor.GetProperty(PackageBranchIdRegexKey);
                if (packageBranchIdRegex == null)
                    throw new HiveException("Missing package branch id.");

                string repositoryRootFormat = descriptor.GetProperty(RepositoryRootFormatKey);
                if (repositoryRootFormat == null)
                    throw new HiveException("Missing repository root format.");

                return new Mapping(new Regex(packageBranchIdRegex), repositoryRootFormat);
            }

            internal Descriptor ToDescriptor()
            {
                var descriptor = new Descriptor();

                descriptor.AddProperty(PackageBranchIdRegexKey, packageBranchIdRegex.ToString());
                descriptor.AddProperty(RepositoryRootFormatKey, repositoryRootFormat);

                return descriptor;
            }
        }
    }
}
